/*
 $Id$

 Copyright (C) 2006-2007 by David Cotton

 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
 Foundation; either version 2 of the License, or (at your option) any later
 version.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
 Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package fr.free.jchecs.core.board;

import static fr.free.jchecs.core.Constants.FILE_COUNT;
import static fr.free.jchecs.core.Constants.RANK_COUNT;
import static fr.free.jchecs.core.Piece.BLACK_BISHOP;
import static fr.free.jchecs.core.Piece.BLACK_KING;
import static fr.free.jchecs.core.Piece.BLACK_KNIGHT;
import static fr.free.jchecs.core.Piece.BLACK_PAWN;
import static fr.free.jchecs.core.Piece.BLACK_QUEEN;
import static fr.free.jchecs.core.Piece.BLACK_ROOK;
import static fr.free.jchecs.core.Piece.WHITE_BISHOP;
import static fr.free.jchecs.core.Piece.WHITE_KING;
import static fr.free.jchecs.core.Piece.WHITE_KNIGHT;
import static fr.free.jchecs.core.Piece.WHITE_PAWN;
import static fr.free.jchecs.core.Piece.WHITE_QUEEN;
import static fr.free.jchecs.core.Piece.WHITE_ROOK;
import static fr.free.jchecs.core.PieceType.BISHOP;
import static fr.free.jchecs.core.PieceType.KING;
import static fr.free.jchecs.core.PieceType.KNIGHT;
import static fr.free.jchecs.core.PieceType.PAWN;
import static fr.free.jchecs.core.PieceType.QUEEN;
import static fr.free.jchecs.core.PieceType.ROOK;
import fr.free.jchecs.core.Piece;
import fr.free.jchecs.core.PieceType;
import fr.free.jchecs.core.Square;
import fr.free.jchecs.core.move.Move;
import fr.free.jchecs.core.move.MoveGenerator;

/**
 * Representing a state of the game based on a two-dimensional array to store
 * positions.
 * <p>
 * This way of coding a game board is more natural and easy to validate, but
 * offers poor performance: it should be avoided in a generation engine blows,
 * but is ideal for unit testing of more complex representations.
 * </p>
 * 
 * @author David Cotton
 */
final class ArrayBoard extends AbstractMoveGenerator {
	/** Instance corresponding to an initial state, without documentation. */
	static final MoveGenerator EMPTY = new ArrayBoard();

	/** Instance corresponding to the initial state standard. */
	static final MoveGenerator STARTING;
	static {
		final ArrayBoard etat = new ArrayBoard();
		for (int x = 0; x < FILE_COUNT; x++) {
			etat._pieces[x][1] = WHITE_PAWN;
			etat._pieces[x][RANK_COUNT - 2] = BLACK_PAWN;
		}
		etat._pieces[0][0] = WHITE_ROOK;
		etat._pieces[1][0] = WHITE_KNIGHT;
		etat._pieces[2][0] = WHITE_BISHOP;
		etat._pieces[3][0] = WHITE_QUEEN;
		etat._pieces[FILE_COUNT - 4][0] = WHITE_KING;
		etat._pieces[FILE_COUNT - 3][0] = WHITE_BISHOP;
		etat._pieces[FILE_COUNT - 2][0] = WHITE_KNIGHT;
		etat._pieces[FILE_COUNT - 1][0] = WHITE_ROOK;
		etat._pieces[0][RANK_COUNT - 1] = BLACK_ROOK;
		etat._pieces[1][RANK_COUNT - 1] = BLACK_KNIGHT;
		etat._pieces[2][RANK_COUNT - 1] = BLACK_BISHOP;
		etat._pieces[3][RANK_COUNT - 1] = BLACK_QUEEN;
		etat._pieces[FILE_COUNT - 4][RANK_COUNT - 1] = BLACK_KING;
		etat._pieces[FILE_COUNT - 3][RANK_COUNT - 1] = BLACK_BISHOP;
		etat._pieces[FILE_COUNT - 2][RANK_COUNT - 1] = BLACK_KNIGHT;
		etat._pieces[FILE_COUNT - 1][RANK_COUNT - 1] = BLACK_ROOK;
		etat.setKingSquare(false, Square.valueOf(4, 7));
		etat.setKingSquare(true, Square.valueOf(4, 0));
		STARTING = etat;
	}

	/** Class identifier for serialization. */
	private static final long serialVersionUID = -7691142320420490263L;

	/** List of modifiers to the movements of a king. */
	private static final int[] KING_MOVES = { -1, 0, 1, 1, 1, 0, -1, -1 };

	/** List of modifiers to the movements of a rider. */
	private static final int[] KNIGHT_MOVES = { -1, 1, 2, 2, 1, -1, -2, -2, };

	/** Description of the platform. */
	private final Piece[][] _pieces = new Piece[FILE_COUNT][RANK_COUNT];

	/** Buffer hash key (may be null). */
	private Integer _hashCode;

	/**
	 * Creates a new instance internally.
	 */
	private ArrayBoard() {
		// Rien de spécifique...
	}

	/**
	 * Crée une nouvelle instance, initialisée à partir de l'état reçu en
	 * paramètre.
	 * 
	 * @param pEtat
	 *            Instance initial.
	 */
	private ArrayBoard(final Board pEtat) {
		super(pEtat);

		for (final Square s : Square.values()) {
			_pieces[s.getFile()][s.getRank()] = pEtat.getPieceAt(s);
		}
	}

	/**
	 * Creates a new instance, copy of proceedings received.
	 * 
	 * @param pEtat
	 *            Instance to copy.
	 */
	private ArrayBoard(final ArrayBoard pEtat) {
		super(pEtat);

		for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */) {
			System.arraycopy(pEtat._pieces[x], 0, _pieces[x], 0, RANK_COUNT);
		}
	}

	/**
	 * Returns a new instance initialized from any state.
	 * 
	 * @param pEtat
	 *            State of departure.
	 * @return Copy of the report.
	 */
	public MoveGenerator derive(final Board pEtat) {
		assert pEtat != null;

		return new ArrayBoard(pEtat);
	}

	/**
	 * Renvoi une nouvelle instance décrivant l'état du jeu après application
	 * d'un mouvement.
	 * 
	 * @param pMouvement
	 *            Description of movement.
	 * @param pSuivant
	 *            Flag set if you want the game goes to half moves along.
	 * @return Instance derived.
	 */
	public MoveGenerator derive(final Move pMouvement, final boolean pSuivant) {
		assert pMouvement != null;

		final ArrayBoard res = new ArrayBoard(this);

		// Ajuste les compteurs...
		if (pSuivant) {
			final boolean t = !isWhiteActive();
			res.setWhiteActive(t);
			if (t) {
				res.setFullmoveNumber(getFullmoveNumber() + 1);
			}
			if (pMouvement.getCaptured() == null) {
				res.setHalfmoveCount(getHalfmoveCount() + 1);
			} else {
				res.setHalfmoveCount(0);
			}
		}
		// Déplace la pièce...
		final Piece piece = pMouvement.getPiece();
		final PieceType typePiece = piece.getType();
		final boolean trait = piece.isWhite();
		final Square src = pMouvement.getFrom();
		final int xSrc = src.getFile();
		final int ySrc = src.getRank();
		assert res._pieces[xSrc][ySrc] == piece;
		res._pieces[xSrc][ySrc] = null;
		final Square dst = pMouvement.getTo();
		final int xDst = dst.getFile();
		final int yDst = dst.getRank();
		res._pieces[xDst][yDst] = piece;
		// ... éxécute un mouvement spécifique de type "roque" et gère le suivi
		// des rois ...
		if (typePiece == KING) {
			res.setKingSquare(trait, dst);
			if (xSrc == 4) {
				if (xDst == 2) {
					// ... côté reine...
					final Piece tour = res._pieces[0][yDst];
					assert tour != null;
					assert tour.getType() == ROOK;
					res._pieces[0][yDst] = null;
					res._pieces[3][yDst] = tour;
					res.setCastled(trait, true);
				} else if (xDst == 6) {
					// ... côté roi...
					final Piece tour = res._pieces[FILE_COUNT - 1][yDst];
					assert tour != null;
					assert tour.getType() == ROOK;
					res._pieces[FILE_COUNT - 1][yDst] = null;
					res._pieces[5][yDst] = tour;
					res.setCastled(trait, true);
				}
			}
		}
		// ... éxécute un mouvement spécifique du type "en passant" ...
		if ((typePiece == PAWN) && (dst == getEnPassant())) {
			if (trait) {
				res._pieces[xDst][yDst - 1] = null;
			} else {
				res._pieces[xDst][yDst + 1] = null;
			}
		}
		// Gére la promotion des pions...
		if (typePiece == PAWN) {
			if (trait) {
				assert yDst > ySrc;
				if (yDst == RANK_COUNT - 1) {
					res._pieces[xDst][yDst] = WHITE_QUEEN;
				}
			} else {
				assert yDst < ySrc;
				if (yDst == 0) {
					res._pieces[xDst][yDst] = BLACK_QUEEN;
				}
			}
		}
		// Tient compte des interdictions de roquer que le mouvement peut
		// provoquer...
		if (canCastleShort(trait)) {
			if ((typePiece == KING)
					|| ((typePiece == ROOK) && (xSrc == FILE_COUNT - 1))) {
				res.setCastleShort(trait, false);
			}
		}
		if (canCastleLong(trait)) {
			if ((typePiece == KING) || ((typePiece == ROOK) && (xSrc == 0))) {
				res.setCastleLong(trait, false);
			}
		}
		// Détecte si une possibilité de prise "en passant" doit être
		// signalée...
		res.setEnPassant(null);
		if (typePiece == PAWN) {
			// En profite pour aussi gérer le compteur de demis coups...
			if (pSuivant) {
				res.setHalfmoveCount(0);
			}
			if (trait) {
				if ((ySrc == 1) && (yDst == 3)) {
					res.setEnPassant(Square.valueOf(xDst, 2));
				}
			} else {
				if ((ySrc == RANK_COUNT - 2) && (yDst == RANK_COUNT - 4)) {
					res.setEnPassant(Square.valueOf(xDst, RANK_COUNT - 3));
				}
			}
		}

		return res;
	}

	/**
	 * Specialized method for testing the equality between two such
	 * descriptions.
	 * 
	 * @param pObjet
	 *            Object with which to compare.
	 * @return True if both objects are equal.
	 */
	@Override
	public boolean equals(final Object pObjet) {
		if (pObjet == this) {
			return true;
		}

		if (pObjet instanceof ArrayBoard) {
			if (hashCode() != pObjet.hashCode()) {
				return false;
			}

			final ArrayBoard o = (ArrayBoard) pObjet;

			for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */) {
				for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */) {
					if (_pieces[x][y] != o._pieces[x][y]) {
						return false;
					}
				}
			}

			return equalsInternal(o);
		}

		return super.equals(pObjet);
	}

	/**
	 * Divert all possible movements of target cells (including those involving
	 * the king in check) for the part contained by a box.
	 * 
	 * @param pOrigine
	 *            Check the origin of the movement.
	 * @return List of target cells (including those leading to a failure).
	 */
	public Square[] getAllTargets(final Square pOrigine) {
		assert pOrigine != null;

		Square[] res = null;

		final Piece piece = _pieces[pOrigine.getFile()][pOrigine.getRank()];
		if (piece != null) {
			final boolean trait = piece.isWhite();
			switch (piece.getType()) {
			case BISHOP:
				res = getBishopTargets(pOrigine, trait);
				break;
			case KING:
				res = getKingTargets(pOrigine, trait);
				break;
			case KNIGHT:
				res = getKnightTargets(pOrigine, trait);
				break;
			case PAWN:
				res = getPawnTargets(pOrigine, trait);
				break;
			case QUEEN:
				res = getQueenTargets(pOrigine, trait);
				break;
			case ROOK:
				res = getRookTargets(pOrigine, trait);
				break;
			default:
				assert false;
			}
		} else {
			res = new Square[0];
		}

		return res;
	}

	/**
	 * Divert all fields possible targets of a movement like "crazy"in a certain
	 * color (including putting the king in check) from a box.
	 * 
	 * @param pOrigine
	 *            Check the origin of the movement.
	 * @param pBlanc
	 *            Set to true if the search for whites.
	 * @return List of target cells (including those leading to a failure).
	 */
	public Square[] getBishopTargets(final Square pOrigine, final boolean pBlanc) {
		assert pOrigine != null;

		final Square[] tmp = new Square[13];
		int nb = 0;

		final int xSrc = pOrigine.getFile();
		final int ySrc = pOrigine.getRank();

		// Mouvements / prise vers le haut/gauche...
		int xDst = xSrc;
		int yDst = ySrc;
		while ((--xDst >= 0) && (++yDst < RANK_COUNT)) {
			final Piece p = _pieces[xDst][yDst];
			if (p == null) {
				tmp[nb++] = Square.valueOf(xDst, yDst);
			} else {
				if (p.isWhite() != pBlanc) {
					tmp[nb++] = Square.valueOf(xDst, yDst);
				}
				break;
			}
		}

		// Mouvements / prise vers le haut/droit...
		xDst = xSrc;
		yDst = ySrc;
		while ((++xDst < FILE_COUNT) && (++yDst < RANK_COUNT)) {
			final Piece p = _pieces[xDst][yDst];
			if (p == null) {
				tmp[nb++] = Square.valueOf(xDst, yDst);
			} else {
				if (p.isWhite() != pBlanc) {
					tmp[nb++] = Square.valueOf(xDst, yDst);
				}
				break;
			}
		}

		// Mouvements / prise vers le bas/gauche...
		xDst = xSrc;
		yDst = ySrc;
		while ((--xDst >= 0) && (--yDst >= 0)) {
			final Piece p = _pieces[xDst][yDst];
			if (p == null) {
				tmp[nb++] = Square.valueOf(xDst, yDst);
			} else {
				if (p.isWhite() != pBlanc) {
					tmp[nb++] = Square.valueOf(xDst, yDst);
				}
				break;
			}
		}

		// Mouvements / prise vers le bas/droit...
		xDst = xSrc;
		yDst = ySrc;
		while ((++xDst < FILE_COUNT) && (--yDst >= 0)) {
			final Piece p = _pieces[xDst][yDst];
			if (p == null) {
				tmp[nb++] = Square.valueOf(xDst, yDst);
			} else {
				if (p.isWhite() != pBlanc) {
					tmp[nb++] = Square.valueOf(xDst, yDst);
				}
				break;
			}
		}

		final Square[] res = new Square[nb];
		System.arraycopy(tmp, 0, res, 0, nb);

		return res;
	}

	/**
	 * Reference list of boxes that can be achieved by a King-type movement.
	 * 
	 * @param pOrigine
	 *            Check the origin of the movement.
	 * @param pBlanc
	 *            A true to indicate a search for whites.
	 * @return List of target cells (including those leading to a failure).
	 */
	public Square[] getKingTargets(final Square pOrigine, final boolean pBlanc) {
		assert pOrigine != null;

		final Square[] tmp = new Square[8];
		int nb = 0;

		final int xSrc = pOrigine.getFile();
		final int ySrc = pOrigine.getRank();
		final int kLength = KING_MOVES.length;
		for (int i = kLength; --i >= 0; /* Pré-décrémenté */) {
			final int xDst = xSrc + KING_MOVES[i];
			final int yDst = ySrc + KING_MOVES[(i + 2) % kLength];
			if ((xDst >= 0) && (yDst >= 0) && (xDst < FILE_COUNT)
					&& (yDst < RANK_COUNT)) {
				final Piece p = _pieces[xDst][yDst];
				if ((p == null) || (p.isWhite() != pBlanc)) {
					tmp[nb++] = Square.valueOf(xDst, yDst);
				}
			}
		}
		if ((nb > 0) && (xSrc == 4)) {
			if (canCastleShort(pBlanc) && (_pieces[5][ySrc] == null)
					&& (_pieces[6][ySrc] == null)) {
				final Piece t = _pieces[FILE_COUNT - 1][ySrc];
				if ((t != null) && (t.getType() == ROOK)
						&& (t.isWhite() == pBlanc)) {
					tmp[nb++] = Square.valueOf(6, ySrc);
				}
			}
			if (canCastleLong(pBlanc) && (_pieces[3][ySrc] == null)
					&& (_pieces[2][ySrc] == null) && (_pieces[1][ySrc] == null)) {
				final Piece t = _pieces[0][ySrc];
				if ((t != null) && (t.getType() == ROOK)
						&& (t.isWhite() == pBlanc)) {
					tmp[nb++] = Square.valueOf(2, ySrc);
				}
			}
		}

		final Square[] res = new Square[nb];
		System.arraycopy(tmp, 0, res, 0, nb);

		return res;
	}

	/**
	 * Reference list of boxes that can be achieved by movement type knight.
	 * 
	 * @param pOrigine
	 *            Check the origin of the movement.
	 * @param pBlanc
	 *            A true to indicate a search for whites.
	 * @return List of target cells (including those leading to a failure).
	 */
	public Square[] getKnightTargets(final Square pOrigine, final boolean pBlanc) {
		assert pOrigine != null;

		final Square[] tmp = new Square[8];
		int nb = 0;

		final int oX = pOrigine.getFile();
		final int oY = pOrigine.getRank();
		final int kLength = KNIGHT_MOVES.length;
		for (int i = kLength; --i >= 0; /* Pré-décrémenté */) {
			final int xDst = oX + KNIGHT_MOVES[i];
			final int yDst = oY + KNIGHT_MOVES[(i + 2) % kLength];
			if ((xDst >= 0) && (yDst >= 0) && (xDst < FILE_COUNT)
					&& (yDst < RANK_COUNT)) {
				final Piece p = _pieces[xDst][yDst];
				if ((p == null) || (p.isWhite() != pBlanc)) {
					tmp[nb++] = Square.valueOf(xDst, yDst);
				}
			}
		}

		final Square[] res = new Square[nb];
		System.arraycopy(tmp, 0, res, 0, nb);

		return res;
	}

	/**
	 * Reference list of boxes that can be achieved by a piece-type movement.
	 * 
	 * @param pOrigine
	 *            Check the origin of the movement.
	 * @param pBlanc
	 *            A true to indicate a search for whites.
	 * @return List of target cells (including those leading to a failure).
	 */
	public Square[] getPawnTargets(final Square pOrigine, final boolean pBlanc) {
		assert pOrigine != null;

		final Square[] tmp = new Square[4];
		int nb = 0;

		final int ySrc = pOrigine.getRank();
		if (pBlanc) {
			if (ySrc < RANK_COUNT - 1) {
				final int xSrc = pOrigine.getFile();
				// Mouvement de 1...
				if (_pieces[xSrc][ySrc + 1] == null) {
					tmp[nb++] = Square.valueOf(xSrc, ySrc + 1);
					// Mouvement initial de 2
					if ((ySrc == 1) && (_pieces[xSrc][3] == null)) {
						tmp[nb++] = Square.valueOf(xSrc, 3);
					}
				}
				if (xSrc > 0) {
					// Prise à gauche (y compris en passant)...
					final Square cDest = Square.valueOf(xSrc - 1, ySrc + 1);
					final Piece pDest = _pieces[xSrc - 1][ySrc + 1];
					if (((pDest != null) && (!pDest.isWhite()))
							|| (cDest == getEnPassant())) {
						tmp[nb++] = cDest;
					}
				}
				if (xSrc < FILE_COUNT - 1) {
					// Prise à droite (y compris en passant)...
					final Square cDest = Square.valueOf(xSrc + 1, ySrc + 1);
					final Piece pDest = _pieces[xSrc + 1][ySrc + 1];
					if (((pDest != null) && (!pDest.isWhite()))
							|| (cDest == getEnPassant())) {
						tmp[nb++] = cDest;
					}
				}
			}
		} else {
			if (ySrc > 0) {
				final int xSrc = pOrigine.getFile();
				// Mouvement de 1...
				if (_pieces[xSrc][ySrc - 1] == null) {
					tmp[nb++] = Square.valueOf(xSrc, ySrc - 1);
					// Mouvement initial de 2
					if ((ySrc == RANK_COUNT - 2)
							&& (_pieces[xSrc][RANK_COUNT - 4] == null)) {
						tmp[nb++] = Square.valueOf(xSrc, RANK_COUNT - 4);
					}
				}
				if (xSrc > 0) {
					// Prise à gauche (y compris en passant)...
					final Square cDest = Square.valueOf(xSrc - 1, ySrc - 1);
					final Piece pDest = _pieces[xSrc - 1][ySrc - 1];
					if (((pDest != null) && pDest.isWhite())
							|| (cDest == getEnPassant())) {
						tmp[nb++] = cDest;
					}
				}
				if (xSrc < FILE_COUNT - 1) {
					// Prise à droite (y compris en passant)...
					final Square cDest = Square.valueOf(xSrc + 1, ySrc - 1);
					final Piece pDest = _pieces[xSrc + 1][ySrc - 1];
					if (((pDest != null) && pDest.isWhite())
							|| (cDest == getEnPassant())) {
						tmp[nb++] = cDest;
					}
				}
			}
		}

		final Square[] res = new Square[nb];
		System.arraycopy(tmp, 0, res, 0, nb);

		return res;
	}

	/**
	 * Reference to any room in this box set.
	 * 
	 * @param pCase
	 *            Box test.
	 * @return This piece on the box (or null if none).
	 */
	public Piece getPieceAt(final Square pCase) {
		assert pCase != null;

		return getPieceAt(pCase.getFile(), pCase.getRank());
	}

	/**
	 * Reference to any room in this box whose coordinates are indicated.
	 * 
	 * @param pColonne
	 *            Column in the box to test (0 to 7).
	 * @param pLigne
	 *            Line of the box tested (0 to 7).
	 * @return This piece on the box (or null).
	 */
	public Piece getPieceAt(final int pColonne, final int pLigne) {
		assert (pColonne >= 0) && (pColonne < FILE_COUNT);
		assert (pLigne >= 0) && (pLigne < RANK_COUNT);

		return _pieces[pColonne][pLigne];
	}

	/**
	 * Divert all fields possible targets of a movement like "lady" of a certain
	 * color (including putting the king in check) from a box.
	 * 
	 * @param pOrigine
	 *            Check the origin of the movement.
	 * @param pBlanc
	 *            Set to true to search for whites.
	 * @return List of target cells (including those leading to a failure).
	 */
	public Square[] getQueenTargets(final Square pOrigine, final boolean pBlanc) {
		assert pOrigine != null;

		final Square[] tour = getRookTargets(pOrigine, pBlanc);
		final int tl = tour.length;
		final Square[] fou = getBishopTargets(pOrigine, pBlanc);
		final int fl = fou.length;

		final Square[] res = new Square[tl + fl];
		System.arraycopy(tour, 0, res, 0, tl);
		System.arraycopy(fou, 0, res, tl, fl);

		return res;
	}

	/**
	 * Divert all fields possible targets of a movement-type "tower" of a
	 * certain color (including putting the king in check) from a box.
	 * 
	 * @param pOrigine
	 *            Check the origin of the movement.
	 * @param pBlanc
	 *            Set to true to search for whites.
	 * @return List of target cells (including those leading to a failure).
	 */
	public Square[] getRookTargets(final Square pOrigine, final boolean pBlanc) {
		assert pOrigine != null;

		final Square[] tmp = new Square[14];
		int nb = 0;

		final int xSrc = pOrigine.getFile();
		final int ySrc = pOrigine.getRank();

		// Mouvements / prise vers la gauche...
		for (int x = xSrc - 1; x >= 0; x--) {
			final Piece p = _pieces[x][ySrc];
			if (p == null) {
				tmp[nb++] = Square.valueOf(x, ySrc);
			} else {
				if (p.isWhite() != pBlanc) {
					tmp[nb++] = Square.valueOf(x, ySrc);
				}
				break;
			}
		}

		// Mouvements / prise vers la droite...
		for (int x = xSrc + 1; x < FILE_COUNT; x++) {
			final Piece p = _pieces[x][ySrc];
			if (p == null) {
				tmp[nb++] = Square.valueOf(x, ySrc);
			} else {
				if (p.isWhite() != pBlanc) {
					tmp[nb++] = Square.valueOf(x, ySrc);
				}
				break;
			}
		}

		// Mouvements / prise vers le haut...
		for (int y = ySrc + 1; y < RANK_COUNT; y++) {
			final Piece p = _pieces[xSrc][y];
			if (p == null) {
				tmp[nb++] = Square.valueOf(xSrc, y);
			} else {
				if (p.isWhite() != pBlanc) {
					tmp[nb++] = Square.valueOf(xSrc, y);
				}
				break;
			}
		}

		// Mouvements / prise vers le bas...
		for (int y = ySrc - 1; y >= 0; y--) {
			final Piece p = _pieces[xSrc][y];
			if (p == null) {
				tmp[nb++] = Square.valueOf(xSrc, y);
			} else {
				if (p.isWhite() != pBlanc) {
					tmp[nb++] = Square.valueOf(xSrc, y);
				}
				break;
			}
		}

		final Square[] res = new Square[nb];
		System.arraycopy(tmp, 0, res, 0, nb);

		return res;
	}

	/**
	 * Divert all movements valid for one color.
	 * 
	 * @param pTrait
	 *            Set to "true" to indicate a search for whites.
	 * @return List of valid movements.
	 */
	public Move[] getValidMoves(final boolean pTrait) {
		final Move[] tmp = new Move[164];
		int nb = 0;

		for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */) {
			for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */) {
				final Piece p = _pieces[x][y];
				if ((p != null) && (p.isWhite() == pTrait)) {
					final Square orig = Square.valueOf(x, y);
					for (final Square dst : getValidTargets(orig)) {
						final Piece prise;
						if ((p.getType() != PAWN) || (dst != getEnPassant())) {
							prise = _pieces[dst.getFile()][dst.getRank()];
						} else {
							if (pTrait) {
								prise = _pieces[dst.getFile()][dst.getRank() - 1];
							} else {
								prise = _pieces[dst.getFile()][dst.getRank() + 1];
							}
						}
						tmp[nb++] = new Move(p, orig, dst, prise);
					}
				}
			}
		}

		final Move[] res = new Move[nb];
		System.arraycopy(tmp, 0, res, 0, nb);

		return res;
	}

	/**
	 * Divert all boxes movements valid targets from a box.
	 * 
	 * @param pOrigine
	 *            Check the origin of the movement.
	 * @return List of target boxes.
	 */
	public Square[] getValidTargets(final Square pOrigine) {
		assert pOrigine != null;

		final Piece piece = _pieces[pOrigine.getFile()][pOrigine.getRank()];
		if (piece != null) {
			final Square[] total = getAllTargets(pOrigine);
			final int tLength = total.length;
			final Square[] tmp = new Square[tLength];
			int nb = 0;
			final boolean trait = piece.isWhite();
			for (int t = tLength; --t >= 0; /* Pré-décrémenté */) {
				final Square cible = total[t];
				final int xDst = cible.getFile();
				final int yDst = cible.getRank();
				final Piece prise = _pieces[xDst][yDst];
				if (!derive(new Move(piece, pOrigine, cible, prise), false)
						.isInCheck(trait)) {
					if ((piece.getType() == KING) && (pOrigine.getFile() == 4)) {
						final int delta = 4 - cible.getFile();
						if ((delta == 2) || (delta == -2)) {
							// Elimine le roque si le roi est en échec ou s'il
							// le serait sur la case
							// intermédiaire...
							if (isInCheck(trait)
									|| derive(
											new Move(piece, pOrigine,
													Square.valueOf(
															4 - (delta / 2),
															cible.getRank())),
											false).isInCheck(trait)) {
								continue;
							}
						}
					}

					tmp[nb++] = cible;
				}
			}

			final Square[] res = new Square[nb];
			System.arraycopy(tmp, 0, res, 0, nb);
			return res;
		}

		return new Square[0];
	}

	/**
	 * Overloading of computing hash keys for optimization.
	 * 
	 * @return Clé de hachage.
	 */
	@Override
	public synchronized int hashCode() {
		if (_hashCode == null) {
			int h = zobristRoot();
			for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */) {
				for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */) {
					final Piece p = _pieces[x][y];
					if (p != null) {
						h ^= ZOBRIST_PIECE_POSITION[p.ordinal()][x + y
								* FILE_COUNT];
					}
				}
			}
			assert h == super.hashCode();

			_hashCode = Integer.valueOf(h);
		}

		return _hashCode.intValue();
	}

	/**
	 * Indicates whether a cell is attacked by a color.
	 * 
	 * @param pCible
	 *            Target box.
	 * @param pCouleur
	 *            Set to "true" to test the attack by whites.
	 * @return True if the box is contested.
	 */
	public boolean isAttacked(final Square pCible, final boolean pCouleur) {
		assert pCible != null;

		final int xSrc = pCible.getFile();
		final int ySrc = pCible.getRank();

		Piece p = null;
		int x = xSrc - 1;
		// Gauche
		while ((x >= 0) && (p == null)) {
			p = _pieces[x--][ySrc];
		}
		if ((p != null) && (p.isWhite() == pCouleur)) {
			final PieceType t = p.getType();
			if ((t == ROOK) || (t == QUEEN)) {
				return true;
			}
		}

		p = null;
		x = xSrc + 1;
		// Droite
		while ((x < FILE_COUNT) && (p == null)) {
			p = _pieces[x++][ySrc];
		}
		if ((p != null) && (p.isWhite() == pCouleur)) {
			final PieceType t = p.getType();
			if ((t == ROOK) || (t == QUEEN)) {
				return true;
			}
		}

		p = null;
		int y = ySrc - 1;
		// Bas
		while ((y >= 0) && (p == null)) {
			p = _pieces[xSrc][y--];
		}
		if ((p != null) && (p.isWhite() == pCouleur)) {
			final PieceType t = p.getType();
			if ((t == ROOK) || (t == QUEEN)) {
				return true;
			}
		}

		p = null;
		y = ySrc + 1;
		// Haut
		while ((y < RANK_COUNT) && (p == null)) {
			p = _pieces[xSrc][y++];
		}
		if ((p != null) && (p.isWhite() == pCouleur)) {
			final PieceType t = p.getType();
			if ((t == ROOK) || (t == QUEEN)) {
				return true;
			}
		}

		p = null;
		x = xSrc - 1;
		y = ySrc - 1;
		// Bas / Gauche
		while ((x >= 0) && (y >= 0) && (p == null)) {
			p = _pieces[x--][y--];
		}
		if ((p != null) && (p.isWhite() == pCouleur)) {
			final PieceType t = p.getType();
			if ((t == BISHOP) || (t == QUEEN)) {
				return true;
			}
		}

		p = null;
		x = xSrc - 1;
		y = ySrc + 1;
		// Haut / Gauche
		while ((x >= 0) && (y < RANK_COUNT) && (p == null)) {
			p = _pieces[x--][y++];
		}
		if ((p != null) && (p.isWhite() == pCouleur)) {
			final PieceType t = p.getType();
			if ((t == BISHOP) || (t == QUEEN)) {
				return true;
			}
		}

		p = null;
		x = xSrc + 1;
		y = ySrc + 1;
		// Haut / Droit
		while ((x < FILE_COUNT) && (y < RANK_COUNT) && (p == null)) {
			p = _pieces[x++][y++];
		}
		if ((p != null) && (p.isWhite() == pCouleur)) {
			final PieceType t = p.getType();
			if ((t == BISHOP) || (t == QUEEN)) {
				return true;
			}
		}

		p = null;
		x = xSrc + 1;
		y = ySrc - 1;
		// Bas / Droit
		while ((x < FILE_COUNT) && (y >= 0) && (p == null)) {
			p = _pieces[x++][y--];
		}
		if ((p != null) && (p.isWhite() == pCouleur)) {
			final PieceType t = p.getType();
			if ((t == BISHOP) || (t == QUEEN)) {
				return true;
			}
		}

		// Cavalier
		int kLength = KNIGHT_MOVES.length;
		for (int i = kLength; --i >= 0; /* Pré-décrémenté */) {
			x = xSrc + KNIGHT_MOVES[i];
			y = ySrc + KNIGHT_MOVES[(i + 2) % kLength];
			if ((x >= 0) && (y >= 0) && (x < FILE_COUNT) && (y < RANK_COUNT)) {
				p = _pieces[x][y];
				if ((p != null) && (p.isWhite() == pCouleur)
						&& (p.getType() == KNIGHT)) {
					return true;
				}
			}
		}

		// Roi
		kLength = KING_MOVES.length;
		for (int i = kLength; --i >= 0; /* Pré-décrémenté */) {
			x = xSrc + KING_MOVES[i];
			y = ySrc + KING_MOVES[(i + 2) % kLength];
			if ((x >= 0) && (y >= 0) && (x < FILE_COUNT) && (y < RANK_COUNT)) {
				p = _pieces[x][y];
				if ((p != null) && (p.isWhite() == pCouleur)
						&& (p.getType() == KING)) {
					return true;
				}
			}
		}

		// Pions...
		if (pCouleur) {
			if (ySrc > 1) {
				if (((xSrc > 0) && (_pieces[xSrc - 1][ySrc - 1] == WHITE_PAWN))
						|| ((xSrc < FILE_COUNT - 1) && (_pieces[xSrc + 1][ySrc - 1] == WHITE_PAWN))) {
					return true;
				}
			}
		} else {
			if (ySrc < RANK_COUNT - 2) {
				if (((xSrc > 0) && (_pieces[xSrc - 1][ySrc + 1] == BLACK_PAWN))
						|| ((xSrc < FILE_COUNT - 1) && (_pieces[xSrc + 1][ySrc + 1] == BLACK_PAWN))) {
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Indicates whether the king of color is in check.
	 * 
	 * @param pCouleur
	 *            Set to "true " for test failure on white, to false otherwise.
	 * @return True if the king is in check.
	 */
	public boolean isInCheck(final boolean pCouleur) {
		final Square posRoi = getKingSquare(pCouleur);

		for (int x = FILE_COUNT; --x >= 0; /* Pré-décrémenté */) {
			for (int y = RANK_COUNT; --y >= 0; /* Pré-décrémenté */) {
				final Piece p = _pieces[x][y];
				if ((p != null) && (p.isWhite() != pCouleur)) {
					final Square test = Square.valueOf(x, y);
					if (test != posRoi) {
						for (final Square s : getAllTargets(test)) {
							if (s == posRoi) {
								return true;
							}
						}
					}
				}
			}
		}

		return false;
	}
}
